﻿//-----------------------------------------------------------------------
// <copyright file="DragDockPanelHost.cs" company="Microsoft Corporation copyright 2008.">
// (c) 2008 Microsoft Corporation. All rights reserved.
// This source is subject to the Microsoft Public License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// </copyright>
// <date>15-Sep-2008</date>
// <author>Martin Grayson</author>
// <summary>A draggable, dockable, expandable host class.</summary>
//-----------------------------------------------------------------------
namespace Blacklight.Silverlight.Controls
{
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Collections.Generic;

    /// <summary>
    /// A draggable, dockable, expandable host class.
    /// </summary>
    public class DragDockPanelHost : Canvas
    {
        #region Private members
        /// <summary>
        /// A local store of the number of rows
        /// </summary>
        private int rows = 1;

        /// <summary>
        /// A local store of the number of columns
        /// </summary>
        private int columns = 1;

        /// <summary>
        /// The panel currently being dragged
        /// </summary>
        private DragDockPanel draggingPanel = null;

        /// <summary>
        /// The currently maxmised panel
        /// </summary>
        private DragDockPanel maximizedPanel = null;

        /// <summary>
        /// Stores the minimized column width.
        /// </summary>
        private double minimizedColumnWidth = 250.0;
        #endregion

        #region Constructors
        /// <summary>
        /// DragDockPanelHost Constructor
        /// </summary>
        public DragDockPanelHost()
        {
            this.Loaded += new RoutedEventHandler(this.DragDockPanelHost_Loaded);
            this.SizeChanged += new SizeChangedEventHandler(this.DragDockPanelHost_SizeChanged);
        }
        #endregion

        #region Public members
        /// <summary>
        /// Gets or sets the width for the minimzed column on the right side
        /// </summary>
        public double MinimizedColumnWidth
        {
            get { return this.minimizedColumnWidth; }
            set { this.minimizedColumnWidth = value; }
        }
        #endregion

        #region DragDockPanelHost events
        /// <summary>
        /// Updates the panel layouts.
        /// </summary>
        /// <param name="sender">The drag dock panel host.</param>
        /// <param name="e">Size changed event args.</param>
        private void DragDockPanelHost_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.UpdatePanelLayout();
        }

        /// <summary>
        /// Sorts out the rows and columns when the control loads.
        /// </summary>
        /// <param name="sender">The drag dock panel host.</param>
        /// <param name="e">Routed event args.</param>
        private void DragDockPanelHost_Loaded(object sender, RoutedEventArgs e)
        {
            // Calculate the number of rows and columns required
            this.rows =
                (int)Math.Floor(Math.Sqrt((double)this.Children.Count));

            this.columns =
                (int)Math.Ceiling((double)this.Children.Count / (double)this.rows);

            int child = 0;

            // Loop through the rows and columns and assign to children
            for (int r = 0; r < this.rows; r++)
            {
                for (int c = 0; c < this.columns; c++)
                {
                    DragDockPanel panel = this.Children[child] as DragDockPanel;

                    // Set starting row and column
                    Grid.SetRow(panel, r);
                    Grid.SetColumn(panel, c);

                    // Hook up panel events
                    panel.DragStarted +=
                        new DragEventHander(this.DragDockPanel_DragStarted);
                    panel.DragFinished +=
                        new DragEventHander(this.DragDockPanel_DragFinished);
                    panel.DragMoved +=
                        new DragEventHander(this.DragDockPanel_DragMoved);
                    panel.Maximized +=
                        new EventHandler(this.DragDockPanel_Maximized);
                    panel.Minimized +=
                        new EventHandler(this.DragDockPanel_Minimized);

                    child++;

                    // if we are on the last child, break out of the loop
                    if (child == this.Children.Count)
                    {
                        break;
                    }
                }

                // if we are on the last child, break out of the loop
                if (child == this.Children.Count)
                {
                    break;
                }
            }
        }
        #endregion

        #region Panel dragging events
        /// <summary>
        /// Keeps a reference to the dragging panel.
        /// </summary>
        /// <param name="sender">The dragging panel.</param>
        /// <param name="args">Drag event args.</param>
        private void DragDockPanel_DragStarted(object sender, DragEventArgs args)
        {
            DragDockPanel panel = sender as DragDockPanel;

            // Keep reference to dragging panel
            this.draggingPanel = panel;
        }

        /// <summary>
        /// Shuffles the panels around.
        /// </summary>
        /// <param name="sender">The dragging panel.</param>
        /// <param name="args">Drag event args.</param>
        private void DragDockPanel_DragMoved(object sender, DragEventArgs args)
        {
            Point mousePosInHost =
                args.MouseEventArgs.GetPosition(this);

            int currentRow =
                (int)Math.Floor(mousePosInHost.Y /
                (this.ActualHeight / (double)this.rows));

            int currentColumn =
                (int)Math.Floor(mousePosInHost.X /
                (this.ActualWidth / (double)this.columns));

            // Stores the panel we will swap with
            DragDockPanel swapPanel = null;

            // Loop through children to see if there is a panel to swap with
            foreach (UIElement child in this.Children)
            {
                DragDockPanel panel = child as DragDockPanel;

                // If the panel is not the dragging panel and is in the current row
                // or current column... mark it as the panel to swap with
                if (panel != this.draggingPanel &&
                    Grid.GetColumn(panel) == currentColumn &&
                    Grid.GetRow(panel) == currentRow)
                {
                    swapPanel = panel;
                    break;
                }
            }

            // If there is a panel to swap with
            if (swapPanel != null)
            {
                // Store the new row and column
                int draggingPanelNewColumn = Grid.GetColumn(swapPanel);
                int draggingPanelNewRow = Grid.GetRow(swapPanel);

                // Update the swapping panel row and column
                Grid.SetColumn(swapPanel, Grid.GetColumn(this.draggingPanel));
                Grid.SetRow(swapPanel, Grid.GetRow(this.draggingPanel));

                // Update the dragging panel row and column
                Grid.SetColumn(this.draggingPanel, draggingPanelNewColumn);
                Grid.SetRow(this.draggingPanel, draggingPanelNewRow);

                // Animate the layout to the new positions
                this.AnimatePanelLayout();
            }
        }

        /// <summary>
        /// Drops the dragging panel.
        /// </summary>
        /// <param name="sender">The dragging panel.</param>
        /// <param name="args">Drag event args.</param>
        private void DragDockPanel_DragFinished(object sender, DragEventArgs args)
        {
            // Set dragging panel back to null
            this.draggingPanel = null;

            // Update the layout (to reset all panel positions)
            this.UpdatePanelLayout();
        }
        #endregion

        #region Panel maximized / minimized events
        /// <summary>
        /// Puts all of the panel back to a grid view.
        /// </summary>
        /// <param name="sender">The minimising panel.</param>
        /// <param name="e">Event args.</param>
        private void DragDockPanel_Minimized(object sender, EventArgs e)
        {
            // Set max'ed panel to null
            this.maximizedPanel = null;

            // Loop through children to disable dragging
            foreach (UIElement child in this.Children)
            {
                DragDockPanel panel = 
                    child as DragDockPanel;
                panel.DraggingEnabled = true;
            }

            // Update sizes and layout
            this.AnimatePanelSizes();
            this.AnimatePanelLayout();
        }

        /// <summary>
        /// Maximises a panel.
        /// </summary>
        /// <param name="sender">the panel to maximise.</param>
        /// <param name="e">Event args.</param>
        private void DragDockPanel_Maximized(object sender, EventArgs e)
        {
            DragDockPanel maximizedPanel = 
                sender as DragDockPanel;

            // Store max'ed panel
            this.maximizedPanel = maximizedPanel;

            // Loop through children to disable dragging
            foreach (UIElement child in this.Children)
            {
                DragDockPanel panel = 
                    child as DragDockPanel;

                panel.DraggingEnabled = false;

                if (panel != this.maximizedPanel)
                {
                    panel.PanelMaximized = false;
                }
            }

            // Update sizes and layout
            this.AnimatePanelSizes();
            this.AnimatePanelLayout();
        }
        #endregion    

        #region Private layout methods
        /// <summary>
        /// Updates the panel layout without animation
        /// This does size and position without animation
        /// </summary>
        private void UpdatePanelLayout()
        {
            if (double.IsInfinity(this.ActualWidth) || double.IsNaN(this.ActualWidth) || this.ActualWidth == 0)
            {
                return;
            }

            // If we are not in max'ed panel mode...
            if (this.maximizedPanel == null)
            {
                // Layout children as per rows and columns
                foreach (UIElement child in this.Children)
                {
                    DragDockPanel panel = (DragDockPanel)child;

                    Canvas.SetLeft(
                        panel,
                        (Grid.GetColumn(panel) * (this.ActualWidth / (double)this.columns)));

                    Canvas.SetTop(
                        panel,
                        (Grid.GetRow(panel) * (this.ActualHeight / (double)this.rows)));

                    panel.Width = (this.ActualWidth / (double)this.columns) - panel.Margin.Left - panel.Margin.Right;

                    panel.Height = (this.ActualHeight / (double)this.rows) - panel.Margin.Top - panel.Margin.Bottom;
                }
            }
            else
            {
                Dictionary<int, DragDockPanel> orderedPanels = new Dictionary<int, DragDockPanel>();

                // Loop through children to order them according to their
                // current row and column...
                foreach (UIElement child in this.Children)
                {
                    DragDockPanel panel = (DragDockPanel)child;

                    orderedPanels.Add(
                        (Grid.GetRow(panel) * this.columns) + Grid.GetColumn(panel),
                        panel);
                }

                // Set initial top of minimized panels to 0
                double currentTop = 0.0;

                // For each of the panels (as ordered in the grid)
                for (int i = 0; i < orderedPanels.Count; i++)
                {
                    // If the current panel is not the maximized panel
                    if (orderedPanels[i] != this.maximizedPanel)
                    {
                        // Set the size of the panel
                        orderedPanels[i].Width = this.minimizedColumnWidth - orderedPanels[i].Margin.Left - orderedPanels[i].Margin.Right;
                        orderedPanels[i].Height = (this.ActualHeight / (double)(this.Children.Count - 1)) - orderedPanels[i].Margin.Top - orderedPanels[i].Margin.Bottom;

                        // Set the position of the panel
                        Canvas.SetLeft(orderedPanels[i], this.ActualWidth - this.minimizedColumnWidth);
                        Canvas.SetTop(orderedPanels[i], currentTop);

                        // Increment top
                        currentTop += this.ActualHeight / (double)(this.Children.Count - 1);
                    }
                    else
                    {
                        // Set the size of the panel
                        orderedPanels[i].Width = this.ActualWidth - this.minimizedColumnWidth - orderedPanels[i].Margin.Left - orderedPanels[i].Margin.Right;
                        orderedPanels[i].Height = this.ActualHeight - orderedPanels[i].Margin.Top - orderedPanels[i].Margin.Bottom;

                        // Set the position of the panel
                        Canvas.SetLeft(orderedPanels[i], 0);
                        Canvas.SetTop(orderedPanels[i], 0);
                    }
                }
            }
        }

        /// <summary>
        /// Animates the panel sizes
        /// </summary>
        private void AnimatePanelSizes()
        {
            if (double.IsInfinity(this.ActualWidth) || double.IsNaN(this.ActualWidth) || this.ActualWidth == 0)
            {
                return;
            }

            // If there is not a maxmized panel...
            if (this.maximizedPanel == null)
            {
                // Animate the panel sizes to row / column sizes
                foreach (UIElement child in this.Children)
                {
                    DragDockPanel panel = (DragDockPanel)child;
                    panel.AnimateSize(
                        (this.ActualWidth / (double)this.columns) - panel.Margin.Left - panel.Margin.Right,
                        (this.ActualHeight / (double)this.rows) - panel.Margin.Top - panel.Margin.Bottom);
                }
            }
            else
            {
                // Loop through the children
                foreach (UIElement child in this.Children)
                {
                    DragDockPanel panel = 
                        (DragDockPanel)child;

                    // Set the size of the non 
                    // maximized children
                    if (panel != this.maximizedPanel)
                    {
                        panel.AnimateSize(
                            this.minimizedColumnWidth - panel.Margin.Left - panel.Margin.Right,
                            (this.ActualHeight / (double)(this.Children.Count - 1)) - panel.Margin.Top - panel.Margin.Bottom);
                    }
                    else 
                    {
                        panel.AnimateSize(
                            this.ActualWidth - this.minimizedColumnWidth - panel.Margin.Left - panel.Margin.Right,
                            this.ActualHeight - panel.Margin.Top - panel.Margin.Bottom);
                    }
                }
            }
        }

        /// <summary>
        /// Animate the panel positions
        /// </summary>
        private void AnimatePanelLayout()
        {
            if (double.IsInfinity(this.ActualWidth) || double.IsNaN(this.ActualWidth) || this.ActualWidth == 0)
            {
                return;
            }

            // If we are not in max'ed panel mode...
            if (this.maximizedPanel == null)
            {
                // Loop through children and size to row and columns
                foreach (UIElement child in this.Children)
                {
                    DragDockPanel panel = (DragDockPanel)child;

                    if (panel != this.draggingPanel)
                    {
                        panel.AnimatePosition(
                            (Grid.GetColumn(panel) * (this.ActualWidth / (double)this.columns)),
                            (Grid.GetRow(panel) * (this.ActualHeight / (double)this.rows)));
                    }
                }
            }
            else
            {
                Dictionary<int, DragDockPanel> orderedPanels = new Dictionary<int, DragDockPanel>();

                // Loop through children to order them according to their
                // current row and column...
                foreach (UIElement child in this.Children)
                {
                    DragDockPanel panel = (DragDockPanel)child;

                    orderedPanels.Add(
                        (Grid.GetRow(panel) * this.columns) + Grid.GetColumn(panel),
                        panel);
                }

                // Set initial top of minimized panels to 0
                double currentTop = 0.0;

                // For each of the panels (as ordered in the grid)
                for (int i = 0; i < orderedPanels.Count; i++)
                {
                    // If the current panel is not the maximized panel
                    if (orderedPanels[i] != this.maximizedPanel)
                    {
                        // Animate the size
                        orderedPanels[i].AnimatePosition(
                            this.ActualWidth - this.minimizedColumnWidth,
                            currentTop);

                        // Increment current top
                        currentTop += this.ActualHeight / (double)(this.Children.Count - 1);
                    }
                    else
                    {
                        // Animate it to 0,0
                        orderedPanels[i].AnimatePosition(0, 0);
                    }
                }
            }
        }
        #endregion
    }
}
